var pilote = (function($) {

	function isnull() {
		for ( var i = 0; i < arguments.length; i++) {
			var arg = arguments[i];
			if (arg !== null && arg !== '')
				return arg;
		}
		return arguments[arguments.length - 1];
	}

	var pilote = {};

	/**
	 * Gestion des erreurs
	 */
	pilote.error = {
		dialog : null,
		errorQueue : [],
		handlers : {}
	};

	/**
	 * Handlers de la fenêtre d'erreur
	 */
	pilote.error.handlers = {
		btnClose : function() {
			$(this).dialog('close');
		},
		btnDetail : function() {
			var root = $(this);
			root.find('.ErrorDetail').toggle();
			pilote.error.handlers.evtResize.call(root, null);
		},
		btnNext : function() {
		},
		btnPrevious : function() {
		},
		evtResize : function(evt) {
			var root = $(this);
			var msg = root.find('.ErrorMessage');
			root.find('.ErrorDetail').css({
				top : (12 + msg.outerHeight(true)) + 'px'
			});
		}
	};

	/**
	 * Affichage de la fenêtre d'erreur, contenant les informations de l'erreur
	 * spécifié ou de la dernière reçu si aucun identifiant n'est renseigné.
	 * 
	 * @param id
	 *            identifiant de l'erreur à afficher
	 */
	pilote.error.show = function(id) {
		if (pilote.error.dialog === null) {
			pilote.error.dialog = $('<div class="ErrorDialog"><div class="ErrorMessage" /><div class="ErrorDetail" /></div>');
		}

		var dlg = pilote.error.dialog.find('.ErrorMessage, .ErrorDetail')
				.empty().end();

		id = id === undefined ? pilote.error.errorQueue.length - 1 : id;
		var err = pilote.error.get(id);
		if (!err)
			return;
		var buttons = {
			'Fermer' : pilote.error.handlers.btnClose
		};

		if (err.code == '412 PRECONDITION FAILED') {
			err.message = pilote.error.errorQueue[id].errorDetail;
			err.detail = null;
		}

		dlg.find('.ErrorMessage').html(
				'<span class="ErrorIndex">[%u/%u]</span>%s'.format(id + 1,
						pilote.error.errorQueue.length, err.message));
		dlg.find('.ErrorDetail').hide();

		if (err.detail) {
			dlg.find('.ErrorDetail').text(err.detail);
			buttons['Détail'] = pilote.error.handlers.btnDetail;
		}

		dlg.dialog({
			title : 'Erreur',
			width : 740,
			height : 235,
			modal : true,
			resizable : true,
			buttons : buttons,
			resize : pilote.error.handlers.evtResize
		});
	};

	/**
	 * Stockage d'une erreur dans la pile du gestionnaire
	 * 
	 * @param error
	 *            Erreur à enregistrer
	 * @param show
	 *            si TRUE affiche de suite l'erreur enregistrée
	 */
	pilote.error.queue = function(error, show) {
		pilote.error.errorQueue.push(error);
		if (show === true) {
			pilote.error.show();
		}
	};

	/**
	 * Renvoie l'erreur correspondant à l'identifiant spécifié. L'erreur
	 * contiendra les propriétés : - code : code de l'erreur, - message :
	 * libellé de l'erreur - detail : détail (stack, ...)
	 * 
	 * Les erreurs reçu du serveur (contenant les champs errorCode, errorCause,
	 * ...) sont mis en forme avant d'être renvoyés.
	 * 
	 * @param id
	 *            identifiant de l'erreur
	 * @return erreur
	 */
	pilote.error.get = function(id) {
		var err = pilote.error.errorQueue[id];
		var r = null;
		if (err.errorCode) {
			r = {
				code : err.errorCode,
				message : isnull(err.errorMessage, ''),
				detail : '%s : %s\n\nDétail :\n%s\n\nStack :\n%s'.format(
						isnull(err.errorCode, '(code absent)'), isnull(
								err.errorCause, ''), isnull(err.errorDetail,
								'Pas de détail disponible'), isnull(
								err.errorStack, 'Pas de stack disponible'))
			};
		} else {
			r = err;
		}

		return r;
	};

	/**
	 * Création d'un proxy simplifiant les appels AJAX. La fonction renvoyé sera
	 * appelé avec les paramètres : - method : nom de la méthode à appeler -
	 * data : objet à envoyer au service AJAX - callback : fonction appelé si
	 * l'appel AJAX est réussi, prenant en paramètre le résultat reçu -
	 * errorCallback (optionnel) : fonction appelé si l'appel AJAX est en erreur -
	 * opts (optionnel) : options de surcharge d'appel AJAX
	 * 
	 * En renseignant un texte dans la propriété "loader" des options de
	 * surcharge, un loader est alors utilisé en conséquence. Celui-ci est de la
	 * forme : <div class="globalLoader"> <div class="loaderMessage">...</div>
	 * </div>
	 * 
	 * Par défaut, l'appel AJAX est asynchrone, en GET, et de type JSON.
	 * 
	 * L'URL du service est contruite en assemblant les paramètres "baseUrl" et
	 * "method" via un "printf", c'est à dire :
	 * 
	 * {URL appelée} = {baseUrl}.format({method})
	 * 
	 * 
	 * @param baseUrl
	 *            format de l'URL du service AJAX
	 * @param defaultOptions
	 *            options par défaut du service
	 * @returns {Function}
	 */
	pilote.ajax = function(baseUrl, defaultOptions) {
		var args, ajaxArgs;
		var handler = function(method, data, callback, errorCallback, opts) {
			args = arguments;
			if (errorCallback !== undefined && !$.isFunction(errorCallback)) {
				opts = errorCallback;
				errorCallback = undefined;
			}

			var o = $.extend({}, handler.options, opts || {}, {
				url : handler.baseUrl.format(method),
				data : data,
				success : callback
			});

			if (o.loader !== undefined) {
				o.beforeSend = pilote.ajax.loaderTrigger;
				o.complete = pilote.ajax.loaderTrigger;
			}

			if (errorCallback !== undefined) {
				o.context = {
					error : errorCallback
				};
			}

			ajaxArgs = o;
			$.ajax(o);
		};

		handler.baseUrl = baseUrl;
		handler.getUrlQuery = function(method, data) {
			var baseUrlMethod = handler.baseUrl.format(method);
			var serialData = $.param(data)
			if (serialData === '') {
				return baseUrlMethod;
			} else {
				var separator = baseUrlMethod.indexOf('?') < 0 ? '?' : '&';
				return '%s%s%s'.format(baseUrlMethod, separator, serialData);
			}
		}
		handler.options = $.extend({
			type : 'GET',
			dataType : 'json',
			async : true,
			cache : false,
			error : pilote.ajax.errorHandler
		}, defaultOptions);

		return handler;
	};

	/**
	 * Handler des options "beforeSend" et "complete" des appels AJAX.
	 */
	pilote.ajax.loaderTrigger = function(xhr, o) {
		if (typeof o === 'string') {
			pilote.ajax.loader('hide');
		} else {
			pilote.ajax.loader('show', o.loader);
		}
	};

	/**
	 * Gestion de l'affichage de mise en attente des appels AJAX. Il est
	 * nécessaire d'appeler autant de fois la fonction avec la valeur 'hide' que
	 * la valeur 'show' pour cacher le loader.
	 * 
	 * @param evt
	 *            'show' ou 'hide' suivant que l'on veut afficher ou cacher le
	 *            loader AJAX
	 * @param msg
	 *            message à faire apparaitre dans le loader
	 */
	pilote.ajax.loader = function(evt, msg) {
		if (this.cache === undefined) {
			this.cache = {};
			this.cache.root = $(
					'<div class="globalLoader"><div class="loaderMessage"></div></div>')
					.appendTo($('body'));
			this.cache.message = this.cache.root.children('div.loaderMessage');
		}

		if (this.cnt === undefined)
			this.cnt = 0;

		if (evt === 'show') {
			this.cnt += 1;
			this.cache.message.text(msg || '');
			;
		} else if (evt === 'hide') {
			this.cnt -= 1;
		}

		if (this.cnt == 1) {
			this.cache.root.fadeIn(200);
		} else if (this.cnt == 0) {
			this.cache.root.fadeOut(200);
		}
	};

	/**
	 * Handler d'erreur pour les appels AJAX.
	 * 
	 */
	pilote.ajax.errorHandler = function(xhr, textStatus, errorThrown) {
		if (textStatus == 'parsererror') {
			pilote.error.queue({
				message : 'Erreur de récupération de la réponse',
				detail : errorThrown
			}, true);
		} else {
			var rt = xhr.responseText;
			try {
				error = $.parseJSON(rt);
				pilote.error.queue(error, true);
			} catch (ex) {
				if (textStatus == 'error') {
					pilote.error.queue({
						message : 'Erreur HTTP %s'.format(xhr.status),
						detail : '[%s] %s :\n%s'.format(xhr.status,
								xhr.statusText, xhr.responseText)
					}, true);
				} else {
					pilote.error.queue({
						message : 'Erreur inconnue',
						detail : rt
					}, true);
				}
			}
		}
	};

	/**
	 * Fonction générique de gestion des fenêtres de dialogues
	 * 
	 * @param msg message à afficher
	 * @param title titre de la boîte de dialogue (par défaut "Informations manquantes")
	 * @param buttons objet contenant les boutons à afficher (et leur callback associés)
	 * @param detail détail du message, placé dans un DIV invisible et affichable
	 * @param closeCallback fonction exécutée à la fermeture de la boîte de dialogue
	 */
	pilote.dialog = function (msg, title, buttons, detail, closeCallback) {
		title = title || 'Informations manquantes';
		var dlg = $('<div></div>').attr('title', title).appendTo('body'),
			height = 'auto',
			btn = buttons || { 'Ok': function () { $(this).dialog('close'); return false; } };

		function resizeHandler(evt) { var root = $(this), msg = root.find('.message'); root.find('.detail').css({ top: (12 + msg.outerHeight(true)) + 'px' }); }

		dlg.html('<div class="message"></div><div class="detail"></div>').children('.message').html(msg);
		if (detail) {
			btn['Détail'] = function () { $(this).find('.detail').toggle(); };
			dlg.children('.detail').html(detail);
			height = 260;
		}

		dlg.dialog({
			buttons: btn,
			close: function(evt){
				dlg.remove();
				closeCallback && closeCallback.apply(this, arguments);
			},
			height: height, 
			modal: true,
			resizable: false,
			resize: resizeHandler,
			title : title,
			width: 500
		});

		resizeHandler.call(dlg, null);
	};

	/**
	 * Affiche une fenêtre d'alerte, avec comme seul bouton "Ok".
	 * 
	 * @param msg message à afficher
	 * @param title titre de la boîte de dialogue (par défaut "Message d'alerte")
	 * @param detail détail du message, placé dans un DIV invisible et affichable
	 * @param closeCallback fonction exécutée à la fermeture de la boîte de dialogue
	 */
	pilote.dialog.warn = function (msg, title, detail, closeCallback) {
		pilote.dialog(msg, title || 'Message d\'alerte', null, detail, closeCallback);
	};

	/**
	 * Affiche une fenêtre d'interrogation, avec comme boutons "oui" et "non".
	 * 
	 * @param msg message à afficher
	 * @param title titre de la boîte de dialogue (par défaut "Message d'alerte")
	 * @param callback fonction associée au bouton "oui"
	 * @param closeCallback fonction exécutée à la fermeture de la boîte de dialogue
	 */
	pilote.dialog.ask = function (msg, title, callback, closeCallback) {
		pilote.dialog(msg, title || 'Message d\'alerte', {
			'Non': function () { $(this).dialog('close'); return false; },
			'Oui': function () { callback.apply(this, arguments); $(this).dialog('close'); return true; }
		}, null, closeCallback);
	};

	/**
	 * Affiche une fenêtre d'information, avec comme seul bouton "Ok".
	 * 
	 * @param msg message à afficher
	 * @param title titre de la boîte de dialogue (par défaut "Message d'alerte")
	 */
	pilote.dialog.info = function (msg, title, callback) {
		pilote.dialog(msg, title || 'Message d\'alerte', null, null, callback);
	};

	/**
	 * Conteneur de l'ensemble des services raccrochés au pilote
	 */
	pilote.services = {};

	return pilote;

})(jQuery);


(function ($) {

	if (pilote.util == undefined) pilote.util = {};

	pilote.util.createPackage = function (path, context) {
		var lst = path.split('.'), o, key;
		for (o = context || window; lst.length > 0; ) {
			var key = lst.shift()
			if (o[key] === undefined) o[key] = {};
			o = o[key];
		}

		return o;
	};

	/**
	 * Crée une nouvelle classe avec une possible classe mère.
	 * 
	 * Afin d'ajouter ou modifier une méthode de la classe utilisez soit :
	 * {@code $.extend(Classe.prototype, {
	 *	nouvelleMethode : function() {}
	 * }); }
	 * ou bien
	 * {@code Classe.prototype.nouvelleMethode = function() {}; }
	 * 
	 * Afin d'appeler une méthode parent au sein d'une méthode réimplémentée,
	 * l'appel se fait par la commande suivante :
	 * {@code this.parent.methode.call(this, arg1, arg2); }
	 * 
	 * @param path - Nom de la classe.
	 * @param extend - Classe à étendre.
	 * @param context - Package dans lequel créer la nouvelle classe.
	 * @return le constructeur de la nouvelle classe.
	 */
	pilote.util.createClass = function (path, extend, context) {
		var lst = path.split('.'), cls = lst.pop(),
			o = pilote.util.createPackage(lst.join('.'), context);
		
		if (o[cls] === undefined) {
			eval(path + ' = function () { this.initialize && this.initialize.apply(this, arguments); };');

			if (extend !== undefined) {
				$.extend(o[cls].prototype, extend.prototype);
				o[cls].prototype.parent = extend.prototype;
			}
			
			return o[cls];
		} else {
			throw new Error('Class <%s> already defined'.format(path));
		}
	};

})(jQuery);